
import 'dart:convert';

import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:im_app/im/socket/SignalWebSocket.dart';
import 'package:im_app/im/webrtc/SignalSession.dart';
import 'package:volume_controller/volume_controller.dart';

import '../pojo/ImPojo.dart';
import '../socket/dto/base/Resp.dart';
import '../socket/dto/business/SendData.dart';
import '../socket/dto/business/WebRtcData.dart';
import '../util/LoggerUtil.dart';
import '../util/Objects.dart';
import 'WebRtcUiEvent.dart';
import 'dto.dart';
import 'SignalService.dart';

class WebRtcClient {

  final _logger = createLogger();

  ImUser user;
  String token;
  String tenantCode;
  String websocketUrl;
  bool isAudio;
  WebRtcUiEvent webRtcUiEvent;
  bool isClosing = false;
  bool isClosed = false;

  String roomName = '';

  SignalWebSocket signalWebSocket;
  SignalService signalService;
  SignalSession signalSession;
  Map<String, RTCPeerConnection> peerRtcConnections = <String, RTCPeerConnection>{};
  MediaStream? localMedialStream;
  VolumeController _volumeController = VolumeController.instance;
  double defaultVoice = 0.5;

  WebRtcClient._({ required this.user, required this.token, required this.tenantCode, required this.websocketUrl,
    required this.isAudio, required this.webRtcUiEvent, required this.signalWebSocket, required this.signalService,
    required this.signalSession});

  factory WebRtcClient(ImUser user, String token, String tenantCode, String websocketUrl, bool isAudio,
      WebRtcUiEvent webRtcUiEvent) {
    var signalWebSocket = SignalWebSocket(websocketUrl);
    var signalSession = SignalSession(user.userId, signalWebSocket);
    var signalService = SignalService(signalSession, signalWebSocket);
    return WebRtcClient._(user: user, token: token, tenantCode: tenantCode, websocketUrl: websocketUrl,
      isAudio: isAudio, webRtcUiEvent: webRtcUiEvent, signalSession: signalSession, signalService: signalService,
      signalWebSocket: signalWebSocket);
  }

  Future<void> start(String? rName) async {
    _logger.i("['WebRtcClient start]");

    isClosing = false;
    await _initSignalClient();
    await openLocalVideo();

    defaultVoice = await _volumeController.getVolume();

    _logger.i("['WebRtcClient start], success");
  }

  //  获取webRtc中已经建立连接的用户
  Set<String> getConnectionUsers() {
    return peerRtcConnections.keys.toSet();
  }

  // 打开本地视频流（或音频流）
  Future<void> openLocalVideo() async {
    _logger.i("openLocalVideo, isAudio: $isAudio");
    if (isAudio) {
      var localMediaStream = await navigator.mediaDevices.getUserMedia({ 'video': false, 'audio': true });
      localMedialStream = localMediaStream;
    } else {
      var localMediaStream = await navigator.mediaDevices.getUserMedia({
        'video': {
          'mandatory': {
            'minWidth': '640', // Provide your own width, height and frame rate here
            'minHeight': '480',
            'minFrameRate': '30',
          },
          'facingMode': 'user',
          'optional': [],
        },
        'audio': true });
      // this.localVideo.srcObject = localMediaStream;
      localMedialStream = localMediaStream;
    }
  }

  void setVoice(double voice) {
    _volumeController.setVolume(voice);
  }

  Future<void> switchVoice() async {
    double voice = await _volumeController.getVolume();
    if (voice == defaultVoice) {
      await _volumeController.setVolume(1.0);
    } else {
      await _volumeController.setVolume(defaultVoice);
    }
  }

  void closeAudio() {
    if (localMedialStream != null) {
      localMedialStream!.getTracks().forEach((track){
        if (track.kind == 'audio') {
          track.enabled = false;
        }
      });
    }
  }

  void openAudio() {
    if (localMedialStream != null) {
      localMedialStream!.getTracks().forEach((track){
        if (track.kind == 'audio') {
          track.enabled = true;
        }
      });
    }
  }

  void closeVideo() {
    if (localMedialStream != null) {
      localMedialStream!.getTracks().forEach((track){
        if (track.kind == 'video') {
          track.enabled = false;
        }
      });
    }
  }

  void openVideo() {
    if (localMedialStream != null) {
      localMedialStream!.getTracks().forEach((track){
        if (track.kind == 'video') {
          track.enabled = true;
        }
      });
    }
  }

  Future<String> createRoom() async {
    var chatType = isAudio ? ChatType.audio : ChatType.video;
    var resp = await signalService.createRoom(chatType);
    roomName = resp.otherInfo??"";
    _logger.i('[WebRtcClient createRoom], roomName: $roomName');
    return resp.otherInfo??"";
  }

  /// 接受到IM 邀请视频（or语音）聊天时，进行入房操作
  Future<OkResp> joinRoom(roomName) async {
    var resp = await signalService.joinRoom(roomName);
    this.roomName = resp.otherInfo??"";
    _logger.i('WebRtcClient joinRoom success], roomName: $roomName');
    return resp;
  }

  /// 向信令服务器发送退房申请
  Future<void> exitRoom() async {
    if (Objects.isNotNull(roomName)) {
      try {
        await signalService.exitRoom(roomName);
      } catch (e) {
        _logger.e('[WebRtcClient exit room]', error: e);
      }

      close();
    }
  }

  Future<void> close() async {
    _logger.i('[WebRtcClient close], $isClosing, closed: $isClosed');
    if (isClosed) return;
    isClosed = true;

    try {
      for (var remotePeerRtcConnection in peerRtcConnections.values) {
        await remotePeerRtcConnection.close();
      }
      peerRtcConnections.clear();
      _logger.i('[WebRtcClient close], close remote rtc connection');

      if (signalWebSocket != null) {
        signalWebSocket.close();
      }
      _logger.i('[WebRtcClient close], close signal webSocket');
      if (localMedialStream != null) {
        await localMedialStream!.dispose();
      }

      _logger.i('[WebRtcClient close], close local medial stream');
      signalWebSocket.close();
      signalSession.close();
    } catch (e) {
      _logger.e('[WebRtcClient close]', error:  e);
    }

    _logger.i('[WebRtcClient close], end!!!!');
  }

  /**************************************websocket 接受消息*******************************************************************/
  Future<PongResp> pingToClient(PingReq pingReq) async {
    _logger.i('[WebRtcClient heart check], pong');
    return PongResp();
  }

  // 接受服务器转发用户入房间请求
  Future<OkResp> receiveJoinRoomReq(WebRtcJoinRoomReq joinRoomReq) async {
    _logger.i('[WebRtcClient receive join room data], roomName: ${joinRoomReq.roomName}');
    await _createOffer(joinRoomReq);
    var resp = OkResp(coder: 0, successDataTime: DateTime.now());
    return resp;
  }

  /// 接受服务端转发用户退出请求
  Future<OkResp> receiveExitRoomReq(WebRtcExitRoomReq exitRoomReq) async {
    _logger.i("[WebRtcClient receive exit room data], roomName: ${exitRoomReq.roomName}");
    var userId = exitRoomReq.userId;
    var peerRtcConnection = peerRtcConnections[userId];
    if (Objects.isNotNull(peerRtcConnection)) {
      peerRtcConnections.remove(userId);
      await peerRtcConnection!.close();

      if (peerRtcConnections.length < 2) {
        webRtcUiEvent.exitRoom(userId!);
      }
    }
    var resp = OkResp(coder: 0, successDataTime: DateTime.now());
    return resp;
  }

  /// 接受服务端转发的Sdp请求
  Future<OkResp> receiveSdp(WebRtcP2pSdpReq sdpReq) async {
    _logger.i("[WebRtcClient receive sdp data], sdpType: ${sdpReq.sdpType}");
    var remoteUserId = sdpReq.userId!;
    var sdpType = sdpReq.sdpType;
    if (sdpType == SdpType.offer) { // 连接端
      var remotePeerConnection = peerRtcConnections[remoteUserId];
      if (remotePeerConnection == null) {
        var offerSdp = RTCSessionDescription(sdpReq.sdp, 'offer');
        remotePeerConnection = await _createPeerRtcConnection(remoteUserId!);
        await remotePeerConnection.setRemoteDescription(offerSdp);
        var answerSdp = await remotePeerConnection.createAnswer();
        await remotePeerConnection.setLocalDescription(answerSdp);
        await signalService.sendSdp(roomName, sdpReq.userId!, answerSdp.sdp??'', SdpType.answer);
      }
    } else if (sdpType == SdpType.answer) { // 发起端
      var localPeerConnection = peerRtcConnections[remoteUserId];
      if (Objects.isNotNull(localPeerConnection)) {
        var answerSdp = RTCSessionDescription(sdpReq.sdp, 'answer');
        await localPeerConnection!.setRemoteDescription(answerSdp);
      }
    } else {
      _logger.e('不支持这种SdpType: $sdpType');
    }
    var resp = OkResp(coder: 0, successDataTime: DateTime.now());
    return resp;
  }

  // 接受服务端转发的Candidate
  Future<OkResp> receiveIceCandidateReq(WebRtcP2pCandidateReq candidateReq) async {
    _logger.i("[WebRtcClient receive iceCandidate data]");
    var remoteUserId = candidateReq.userId;
    var peerRtcConnection = peerRtcConnections[remoteUserId];
    if (Objects.isNotNull(peerRtcConnection)) {
      var candidate = RTCIceCandidate(
          candidateReq.iceCandidate.candidate,
          candidateReq.iceCandidate.sdpMid,
          candidateReq.iceCandidate.sdpMLineIndex);
      await peerRtcConnection?.addCandidate(candidate);
    }
    var resp = OkResp(coder: 0, successDataTime: DateTime.now());
    return resp;
  }

  /// 接受服务端转发删除操作
  Future<OkResp> serverSendRoomDel(WebRtcDelRoomReq delRoomReq) async {
    _logger.i("[WebRtcClient receive del room req], roomName: ${delRoomReq.roomName}, userId: ${delRoomReq.userId}");
    var userId = delRoomReq.userId;
    webRtcUiEvent.exitRoom(userId!);
    var resp = OkResp(coder: 0, successDataTime: DateTime.now());
    return resp;
  }

  /*********************************************************************************************************/

  Future<RTCPeerConnection> _createPeerRtcConnection(String remoteUserId) async {
    RTCPeerConnection? peerRtcConnection = peerRtcConnections[remoteUserId];
    if (Objects.isNull(peerRtcConnection)) {
      peerRtcConnection = await _newPeerRtcConnection();
      peerRtcConnection!.onConnectionState = _connectionStateChange;
      peerRtcConnection!.onIceCandidate = (RTCIceCandidate candidate) async { //this.handleLocalIceCandidate.bind(this);
        _logger.i('[WebRtcClient local Candidate change], remoteUserId: $remoteUserId');
        if (Objects.isNotNull(candidate)) {
          IceCandidate tmpCandidate = IceCandidate(sdpMid: candidate.sdpMid!,
              sdpMLineIndex: candidate.sdpMLineIndex??-1, candidate:
              candidate.candidate??'');
          await signalService.iceCandidate(roomName, remoteUserId, tmpCandidate);
        } else {
          _logger.i('[WebRtcClient End of candidates]');
        }
      };
      try {
        localMedialStream!.getTracks().forEach((track) {
          _logger.i('[WebRtcClient open add track]');
          peerRtcConnection!.addTrack(track, localMedialStream!);
          // setupVideoCodec(peerRtcConnection);
        });
      } catch (e) {
        peerRtcConnection!.addStream(localMedialStream!);
        // setupVideoCodec(peerRtcConnection);
      }
      peerRtcConnection!.onTrack = (RTCTrackEvent event) {
        _logger.i('[WebRtcClient receive track]');
        webRtcUiEvent.receiveMedialStream(remoteUserId, event.track.kind??'', event.streams[0], );
        // setupVideoCodec(peerRtcConnection!);
      };
      peerRtcConnections[remoteUserId] = peerRtcConnection;
    }
    return peerRtcConnection!;
  }

  Future<void> _createOffer(WebRtcJoinRoomReq joinRoomReq) async {
    var remoteUserId = joinRoomReq.userId!;
    var localPeerConnection = await _createPeerRtcConnection(remoteUserId);
    RTCSessionDescription offerSdp = await localPeerConnection.createOffer();
    await localPeerConnection.setLocalDescription(offerSdp);
   _logger.i('[WebRtcClient create offer], offerSdp: ${offerSdp.type}, sdp: ${offerSdp.sdp}');
    await signalService.sendSdp(roomName, remoteUserId, offerSdp.sdp??'', SdpType.offer);
  }

  void _connectionStateChange(RTCPeerConnectionState state) {
   _logger.i('[WebRtcClient connection state change], state: ${state.name}');
  }

  Future<RTCPeerConnection> _newPeerRtcConnection() async {
    RTCPeerConnection peerConnection = await createPeerConnection({});
    return peerConnection;// 'sdpSemantics': 'unified-plan'
  }

  Future<void> _initSignalClient() async {
    _initCallBack();

    await signalWebSocket.start();
    await signalService.login(user.userId, token, tenantCode);
  }
  
  void _initCallBack() {
    signalWebSocket.callback("pingToClient", PingReq.fromJson, pingToClient);
    signalWebSocket.callback("serverSendRoomJoin", WebRtcJoinRoomReq.fromJson, receiveJoinRoomReq);
    signalWebSocket.callback("serverSendRoomExit", WebRtcExitRoomReq.fromJson, receiveExitRoomReq);
    signalWebSocket.callback("serverSendSdp", WebRtcP2pSdpReq.fromJson, receiveSdp);
    signalWebSocket.callback("serverSendCandidate", WebRtcP2pCandidateReq.fromJson, receiveIceCandidateReq);
    signalWebSocket.callback("serverSendRoomDel", WebRtcDelRoomReq.fromJson, serverSendRoomDel);
  }
}